package org.example.aio;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.concurrent.CountDownLatch;

/**
 * <p>  </p>
 *
 * @author Chris
 * @since 2024/3/24 13:35
 */
public class AsyncTimeServerHandler implements Runnable {
    /**
     * 使用 slf4j 日志框架
     */
    private final static Logger log = LoggerFactory.getLogger(AsyncTimeServerHandler.class);

    private final int port;
    CountDownLatch latch;
    AsynchronousServerSocketChannel asynchronousServerSocketChannel;

    public AsyncTimeServerHandler(int port) {
        this.port = port;

        try {
            this.asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
            this.asynchronousServerSocketChannel.bind(new InetSocketAddress(this.port));
            log.info("The time server is start in port: {}", port);
        } catch (IOException e) {
            log.error("error=", e);
        }
    }

    @Override
    public void run() {
        this.latch = new CountDownLatch(1);
        this.doAccept();
        try {
            latch.await();
        } catch (InterruptedException e) {
            log.error("error=", e);
        }
    }

    private void doAccept() {
        this.asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());
    }

    private static class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncTimeServerHandler> {

        @Override
        public void completed(AsynchronousSocketChannel result, AsyncTimeServerHandler attachment) {
            attachment.asynchronousServerSocketChannel.accept(attachment, this);
            final ByteBuffer buffer = ByteBuffer.allocate(1024);
            result.read(buffer, buffer, new ReadCompletionHandler(result));
        }

        @Override
        public void failed(Throwable exc, AsyncTimeServerHandler attachment) {
            log.error("error=", exc);
            attachment.latch.countDown();
        }

        private static class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {

            private final AsynchronousSocketChannel channel;

            public ReadCompletionHandler(AsynchronousSocketChannel channel) {
                this.channel = channel;
            }

            @Override
            public void completed(Integer result, ByteBuffer attachment) {
                attachment.flip();
                final byte[] body = new byte[attachment.remaining()];
                attachment.get(body);
                final String req = new String(body, StandardCharsets.UTF_8);
                log.info("The time server receive order: {}", new String(body, StandardCharsets.UTF_8));

                final String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req)
                        ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
                this.doWrite(currentTime);
            }

            private void doWrite(String currentTime) {
                if (StringUtils.isBlank(currentTime)) {
                    return;
                }
                final byte[] bytes = currentTime.getBytes();
                final ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
                writeBuffer.put(bytes);
                writeBuffer.flip();
                this.channel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer result, ByteBuffer attachment) {
                        if (attachment.hasRemaining()) {
                            channel.write(attachment, attachment, this);
                        }
                    }

                    @Override
                    public void failed(Throwable exc, ByteBuffer attachment) {
                        try {
                            channel.close();
                        } catch (IOException e) {
                            log.error("error=", e);
                        }
                    }
                });
            }

            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                try {
                    this.channel.close();
                } catch (IOException e) {
                    log.error("error=", e);
                }
            }
        }
    }
}